using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Storage;

namespace EuphoricEngine3D
{
    public static class TextHandler
    {
        #region Declarations and Properties
        // A dictionary to hold all of the SpriteFont objects we may use to draw text
        private static Dictionary<string, SpriteFont> Fonts = new Dictionary<string, SpriteFont>();

        // The current font we are drawing in when the call to DrawText does not include a font
        // specification (Can be overridden by specifying a font in the DrawText call.)
        private static string sCurrentFont = "";

        // The current color to use when drawing text (Can be overridden by specifying a color in the
        // DrawText call.
        private static Color cCurrentColor = Color.White;

        // When using the "Print" function, CursorLocation tracks the current drawing position for future
        // calls to the Print command.
        public static Vector2 CursorLocation { get; set; }

        // Whenever we draw text, we will use the CurrentFont if we haven't specified otherwise.
        static public string CurrentFont
        {
            get { return sCurrentFont; }
            set
            {
                if (Fonts.ContainsKey(value))
                    sCurrentFont = value;
            }
        }

        // Whenever we draw text, we will use the CurrentColor if we haven't specified otherwise.
        static public Color CurrentColor
        {
            get { return cCurrentColor; }
            set { cCurrentColor = value; }
        }

        // We need a SpriteBatch object to do any drawing.
        static public SpriteBatch spriteBatch { get; set; }

        // We will use a ContentManager object to load our SpriteFonts.
        static public ContentManager contentManager { get; set; }

        #endregion

        #region SpriteFont Loading Methods
        // Load a SpriteFont given the FontName and the ResourceName (Usually identical)
        static public void LoadFont(string FontName, string ResourceName)
        {
            // Throw an exception if we don't have a content manager.
            if (contentManager == null)
                throw new Exception("TextHandler ContentManager Not Initialized");

            Fonts.Add(FontName, contentManager.Load<SpriteFont>(ResourceName));
            if (CurrentFont == "")
                CurrentFont = FontName;
        }

        // Load multiple fonts by passing a folder and a semi-colon delimited list of fonts to load.
        // For example, if your content folder is "Content\Fonts" and you want to load Pericles10 and Pericles12,
        // Pass @"Fonts\" as the FontsFolderPrevis and "Pericles10;Pericles12" as the FontList
        static public void LoadFonts(string FontsFolderPrefix, string FontList)
        {
            // Throw an exception if we don't have a content manager.
            if (contentManager == null)
                throw new Exception("TextHandler ContentManager Not Initialized");

            string[] FontsToLoad = FontList.Split(';');
            foreach (string s in FontsToLoad)
                LoadFont(s.Trim(), FontsFolderPrefix + s.Trim());
        }

        // Automatically loads all SpriteFonts from the passed folder (as a subfolder of the
        // content manager root folder (ie, if your fonts are in "Content\Fonts" and your ContentManager's
        // base folder is "Content", pass "Fonts" as the ContentFontsFolder.
        static public void AutoLoadFonts(string ContentFontsFolder)
        {
            // Throw an exception if we don't have a content manager.
            if (contentManager == null)
                throw new Exception("TextHandler ContentManager Not Initialized");

            // Get all of the files in the passed folder (they will be .XNB files)
            // Note that it will try to load any .XNB files in this folde as SpriteFonts, so that
            // should be the only type of content located there.
            DirectoryInfo info = new DirectoryInfo(contentManager.RootDirectory + @"\" + ContentFontsFolder);
            foreach (FileInfo file in info.GetFiles("*.XNB"))
            {
                // Trim off the .XNB extension
                string sFontName = file.Name.Substring(0, file.Name.Length - 4);

                // Load the font, giving it a dictionary name equal to the .spritefont asset name.
                LoadFont(sFontName, ContentFontsFolder + @"\" + sFontName);
            }
        }
        #endregion

        #region Class Initialization
        // Initializes the TextHandler class for usage.  Call this in your game's LoadContent.  If you pass
        // a folder prefix (ie, @"Fonts\") the class will automatically load all spritefonts in that content folder.
        static public void Initialize(SpriteBatch spriteBatch, ContentManager contentManager, string FontsFolderPrefix)
        {
            TextHandler.spriteBatch = spriteBatch;
            TextHandler.contentManager = contentManager;
            if (FontsFolderPrefix != "")
                AutoLoadFonts(FontsFolderPrefix);
        }
        #endregion

        #region DrawText and it's Overloads
        // Draw a text string.  This is our full method, with all parameters specifiable.  All of the other
        // DrawText methods will be overloads of this function.
        static public void DrawText(string Text, int X, int Y, float Angle,
            string Font, Color color, Vector2 Origin, float Scale,
            SpriteEffects spriteEffects, float LayerDepth)
        {
            // Throw an exception if we don't have a spritebatch.
            if (spriteBatch == null)
                throw new Exception("TextHandler SpriteBatch Not Initialized");

            // Make sure the font exists in our font dictionary.  Otherwise, default to the CurrentFont
            string FontToUse = Font;
            if (!Fonts.ContainsKey(FontToUse))
                FontToUse = CurrentFont;

            // Use SpriteBatch to draw the text string.
            spriteBatch.DrawString(Fonts[FontToUse],
                Text,
                new Vector2(X, Y),
                color,
                Angle,
                Vector2.Zero,
                Scale,
                spriteEffects,
                LayerDepth);
        }

        // ***
        // All of these overloads simply pass defaults along to DrawText above, allowing you to specify
        // different parameters when calling DrawText

        static public void DrawText(string Text, int X, int Y)
        {
            DrawText(Text, X, Y, 0, CurrentFont, CurrentColor, Vector2.Zero, 1.0f, SpriteEffects.None, 0);
        }

        static public void DrawText(string Text, int X, int Y, string Font)
        {
            DrawText(Text, X, Y, 0, Font, CurrentColor, Vector2.Zero, 1.0f, SpriteEffects.None, 0);
        }

        static public void DrawText(string Text, int X, int Y, Color color)
        {
            DrawText(Text, X, Y, 0, CurrentFont, color, Vector2.Zero, 1.0f, SpriteEffects.None, 0);
        }

        static public void DrawText(string Text, int X, int Y, string Font, Color color)
        {
            DrawText(Text, X, Y, 0, Font, color, Vector2.Zero, 1.0f, SpriteEffects.None, 0);
        }
        #endregion

        #region "Print" function - Keeps a cursor location

        static public void Print(string Text)
        {
            DrawText(Text, (int)CursorLocation.X, (int)CursorLocation.Y);
            CursorLocation += new Vector2(0, Fonts[CurrentFont].MeasureString(Text).Y);
        }
        #endregion

        #region Parsed Text

        // Allows special codes to be embedded in a text string to allow for color and font changes.
        // The currently implemented codes are:
        //
        // ![#FFFFFF] - Specifies a color in hex (html-style) notation.  (ie, #FFFFFF=white, #000000=black)
        // ![F:FontName] - Change to the specified font
        // ![\n] - Created manually or automatically... specifies a new line
        static public void DrawTextParsed(string Text, int X, int Y, string Font, int WrapLength)
        {
            // We will use the CursorLocaion to draw our string correctly, so we need to preserve the
            // location so we can restore it after we are done.
            Vector2 SaveCursor = CursorLocation;

            // Preserve the current DefaultColor
            Color SaveColor = CurrentColor;

            // Preserve the current Font
            string SaveFont = CurrentFont;

            // Set the cursor to our drawing location
            CursorLocation = new Vector2(X, Y);

            // Set the Desired Font
            CurrentFont = Font;

            string PreText, MidText, PostText;

            string WorkText = Text;

            // If a Wrap Length has been passed, call our WrapText function and replace all of the
            // newline characters (\n) with ![\n] so they are "coded" with our standard code format.
            if (WrapLength > 0)
                WorkText = WrapText(WorkText, CurrentFont, WrapLength, true).Replace("\n", "![\n]");

            // Loop while the working text string contains a formatting code.
            while (WorkText.Contains("!["))
            {
                // PreText holds the portion of the string before the code.
                PreText = WorkText.Substring(0, WorkText.IndexOf("!["));

                MidText = WorkText.Substring(WorkText.IndexOf("![") + 2);

                // PostText holds the portion of the string after the code.
                PostText = MidText.Substring(MidText.IndexOf("]") + 1);

                // MidText holds the code itself
                MidText = MidText.Substring(0, (MidText.Length - PostText.Length) - 1);

                // Reset WorkText to contain everything after the code.
                WorkText = PostText;

                // Draw PreText at the current location, in the current font and color.
                DrawText(PreText, (int)CursorLocation.X, (int)CursorLocation.Y);

                // Move the cursor horizontally to the end of what we just drew.
                CursorLocation += new Vector2(Fonts[CurrentFont].MeasureString(PreText).X, 0);

                // If we have a formatting code...
                if (MidText.Length > 0)
                {
                    // The # sign signifies an HTML-like color code (ie, #FFFFFF = White)
                    if (MidText.StartsWith("#"))
                    {
                        // Pull out the characters
                        byte r = byte.Parse(MidText.Substring(1, 2), System.Globalization.NumberStyles.HexNumber);
                        byte g = byte.Parse(MidText.Substring(3, 2), System.Globalization.NumberStyles.HexNumber);
                        byte b = byte.Parse(MidText.Substring(5, 2), System.Globalization.NumberStyles.HexNumber);

                        // Set current color to the desired color.
                        CurrentColor = new Color(r, g, b);
                    }
                    // A code beginning with "F:" designates a font change.
                    if (MidText.StartsWith("F:"))
                    {
                        // Set the current font to the desired font.
                        CurrentFont = MidText.Substring(2);
                    }
                    // The newline codes we wrapped earlier:
                    if (MidText == "\n")
                    {
                        // Reset the horizontal position of the cursor, and bring the vertical position down
                        // by the size of the PreText line.
                        CursorLocation = new Vector2(X, CursorLocation.Y + Fonts[CurrentFont].MeasureString(PreText).Y);
                    }
                }
            }

            // We have no codes left in WorkText, so simply draw whatever portion of it is left.
            DrawText(WorkText, (int)CursorLocation.X, (int)CursorLocation.Y);

            // Restore the saved positions
            CursorLocation = SaveCursor;
            CurrentColor = SaveColor;
            CurrentFont = SaveFont;
        }

        // Same as above, but only allows color formating!
        // IE: ![#FF0000] == Red
        static public void DrawTextParsedColored(string Text, int X, int Y, string Font, int WrapLength)
        {
            // We will use the CursorLocaion to draw our string correctly, so we need to preserve the
            // location so we can restore it after we are done.
            Vector2 SaveCursor = CursorLocation;

            // Preserve the current DefaultColor
            Color SaveColor = CurrentColor;

            // Preserve the current Font
            string SaveFont = CurrentFont;

            // Set the cursor to our drawing location
            CursorLocation = new Vector2(X, Y);

            // Set the Desired Font
            CurrentFont = Font;

            string PreText, MidText, PostText;

            string WorkText = Text;

            // If a Wrap Length has been passed, call our WrapText function and replace all of the
            // newline characters (\n) with ![\n] so they are "coded" with our standard code format.
            if (WrapLength > 0)
                WorkText = WrapText(WorkText, CurrentFont, WrapLength, true).Replace("\n", "![\n]");

            // Loop while the working text string contains a formatting code.
            while (WorkText.Contains("![") && WorkText.Contains("]"))
            {
                // PreText holds the portion of the string before the code.
                PreText = WorkText.Substring(0, WorkText.IndexOf("!["));

                MidText = WorkText.Substring(WorkText.IndexOf("![") + 2);

                // PostText holds the portion of the string after the code.
                PostText = MidText.Substring(MidText.IndexOf("]") + 1);

                // MidText holds the code itself
                MidText = MidText.Substring(0, (MidText.Length - PostText.Length) - 1);

                // Reset WorkText to contain everything after the code.
                WorkText = PostText;

                // Draw PreText at the current location, in the current font and color.
                DrawText(PreText, (int)CursorLocation.X, (int)CursorLocation.Y);

                // Move the cursor horizontally to the end of what we just drew.
                CursorLocation += new Vector2(Fonts[CurrentFont].MeasureString(PreText).X, 0);

                // If we have a formatting code...
                if (MidText.Length > 0)
                {
                    // The # sign signifies an HTML-like color code (ie, #FFFFFF = White)
                    if (MidText.StartsWith("#"))
                    {
                        // Pull out the characters
                        byte r = byte.Parse(MidText.Substring(1, 2), System.Globalization.NumberStyles.HexNumber);
                        byte g = byte.Parse(MidText.Substring(3, 2), System.Globalization.NumberStyles.HexNumber);
                        byte b = byte.Parse(MidText.Substring(5, 2), System.Globalization.NumberStyles.HexNumber);

                        // Set current color to the desired color.
                        CurrentColor = new Color(r, g, b);
                    }
                    // The newline codes we wrapped earlier:
                    if (MidText == "\n")
                    {
                        // Reset the horizontal position of the cursor, and bring the vertical position down
                        // by the size of the PreText line.
                        CursorLocation = new Vector2(X, CursorLocation.Y + Fonts[CurrentFont].MeasureString(PreText).Y);
                    }
                }
            }

            // We have no codes left in WorkText, so simply draw whatever portion of it is left.
            DrawText(WorkText, (int)CursorLocation.X, (int)CursorLocation.Y);

            // Restore the saved positions
            CursorLocation = SaveCursor;
            CurrentColor = SaveColor;
            CurrentFont = SaveFont;
        }
        #endregion

        #region Text Wrapping and Formatting

        // Returns a string with all of our "special" formatting codes ( ![CODE] ) stripped out.
        // We will use this when measureing text for wrapping purposes to make sure our codes don't
        // impact our line lengths.
        static public string StripTextCodes(string Text)
        {
            string ResultText = "";
            string WorkText = Text;
            // loop until there is not an instance of "![" in the string
            while (WorkText.Contains("![") && WorkText.Contains("]"))
            {
                // pull apart the string, removing the ![, the ], and the code between them from the text
                string PreText = WorkText.Substring(0, WorkText.IndexOf("!["));
                string MidText = WorkText.Substring(WorkText.IndexOf("![") + 2);
                string PostText = MidText.Substring(MidText.IndexOf("]") + 1);
                MidText = MidText.Substring(0, (MidText.Length - PostText.Length) - 1);
                WorkText = PostText;
                // Append the actual text to our result
                ResultText += PreText;
            }
            ResultText += WorkText;
            // return the string with the codes stripped out.
            return ResultText;
        }

        static public string StripTextCodes(string Text, out int StripCharCount)
        {
            int length = Text.Length;

            string ResultText = "";
            string WorkText = Text;
            // loop until there is not an instance of "![" in the string
            while (WorkText.Contains("![") && WorkText.Contains("]"))
            {
                // pull apart the string, removing the ![, the ], and the code between them from the text
                string PreText = WorkText.Substring(0, WorkText.IndexOf("!["));
                string MidText = WorkText.Substring(WorkText.IndexOf("![") + 2);
                string PostText = MidText.Substring(MidText.IndexOf("]") + 1);
                MidText = MidText.Substring(0, (MidText.Length - PostText.Length) - 1);
                WorkText = PostText;
                // Append the actual text to our result
                ResultText += PreText;
            }
            ResultText += WorkText;

            StripCharCount = length - ResultText.Length;

            // return the string with the codes stripped out.
            return ResultText;
        }

        // I've seen the code that was the basis for this method floating around several places
        // on the web.  As best I can tell it may have originally come from Nick Gravelyn (http://nickgravelyn.com/)
        // but I'm not sure if that is the case.
        //
        // In any case, it returns a string that has newline characters (\n) inserted into it at the points where
        // they would need to go to wrap a text string to a certain width.
        static public string WrapText(string Text, string FontName, float MaxLineWidth, bool StripCodes)
        {
            // Create an array (words) with one entry for each word in the passed text string
            string[] words = Text.Split(' ');

            // A StringBuilder lets us add to a string and finally return the result
            StringBuilder sb = new StringBuilder();

            // How long is the line we are currently working on so far
            float lineWidth = 0.0f;

            // Store a measurement of the size of a space in the font we are using.
            float spaceWidth = Fonts[FontName].MeasureString(" ").X;

            // Loop through each word in the string
            foreach (string word in words)
            {
                Vector2 size;
                // Strip out formatting codes if necessary
                if (StripCodes)
                    size = Fonts[FontName].MeasureString(StripTextCodes(word));
                else
                    size = Fonts[FontName].MeasureString(word);

                // If this word will fit on the current line, add it and keep track
                // of how long the line has gotten.
                if (lineWidth + size.X < MaxLineWidth)
                {
                    sb.Append(word + " ");
                    lineWidth += size.X + spaceWidth;
                }
                else
                // otherwise, append a newline character to start a new line.  Add the
                // word and a space, and set the size of the new line.
                {
                    sb.Append("\n" + word + " ");
                    lineWidth = size.X + spaceWidth;
                }
            }
            // return the resultant string
            return sb.ToString();
        }

        // Overloads for WrapText that supply defaults for some parameters
        static public string WrapText(string Text, float MaxLineWidth)
        {
            return WrapText(Text, CurrentFont, MaxLineWidth, false);
        }

        static public string WrapText(string Text, string FontName, float MaxLineWidth)
        {
            return WrapText(Text, FontName, MaxLineWidth, false);
        }
        #endregion

        public static SpriteFont GetFont(string fontName)
        {
            if (Fonts.Count > 0)
            {
                if (Fonts.ContainsKey(fontName))
                    return Fonts[fontName];

                return Fonts.First().Value;
            }

            return null;
        }
    }
}